package pl.edu.agh.abd.hsmest;

import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.PriorityQueue;

import org.apache.axis.message.addressing.Address;
import org.apache.axis.message.addressing.EndpointReferenceType;
import org.apache.axis.types.URI.MalformedURIException;

import pl.cyfronet.gforge.hsmest.hsmmon.stubs.CreateResource;
import pl.cyfronet.gforge.hsmest.hsmmon.stubs.CreateResourceResponse;
import pl.cyfronet.gforge.hsmest.hsmmon.stubs.HSMDriveState;
import pl.cyfronet.gforge.hsmest.hsmmon.stubs.HSMFileInfo;
import pl.cyfronet.gforge.hsmest.hsmmon.stubs.HSMLibrary;
import pl.cyfronet.gforge.hsmest.hsmmon.stubs.HSMMonFactoryPortType;
import pl.cyfronet.gforge.hsmest.hsmmon.stubs.HSMMonPortType;
import pl.cyfronet.gforge.hsmest.hsmmon.stubs.HSMState;
import pl.cyfronet.gforge.hsmest.hsmmon.stubs.LibrariesInfo;
import pl.cyfronet.gforge.hsmest.hsmmon.stubs.Slot;
import pl.cyfronet.gforge.hsmest.hsmmon.stubs.Tape;
import pl.cyfronet.gforge.hsmest.hsmmon.stubs.TapeArray;
import pl.cyfronet.gforge.hsmest.hsmmon.stubs.VoidType;
import pl.cyfronet.gforge.hsmest.hsmmon.stubs.service.HSMMonFactoryServiceAddressingLocator;
import pl.cyfronet.gforge.hsmest.hsmmon.stubs.service.HSMMonServiceAddressingLocator;
import pl.edu.agh.abd.hsm.EstimatorResponse;
import pl.edu.agh.abd.hsmest.event.FileLoaded;
import pl.edu.agh.abd.hsmest.event.TapeInserted;
import pl.edu.agh.abd.hsmest.event.TapeRemoved;
import pl.edu.agh.abd.hsmest.event.TapeRollback;


//MAIN CLASS
//estimate(filename) returns estimated access time
//
public class Estimator {
	//    hostnanem:/hasdfasdf/plik.txt
	
	//  localhost:8182/estimate/hostname:/asdfasdf/asdfasdf/plik.txt

	public static final int USAGE_UNKNOWN = 0;
	public static final int USAGE_EMPTY = 1;
	public static final int USAGE_INUSE = 2;
	public static final int USAGE_IDLE = 3;
	
	private String serviceAddress = "http://localhost:8080/wsrf/services/HSMMonFactoryService";
	private HSMMonPortType hsmMon = null;

	private HSMLibrary desiredLibrary = null;
	private HSMState state = null;
	private long time = 0;
	private boolean libraryBusy = false;
	private PriorityQueue<Event> eventQueue = new PriorityQueue<Event>();
	
	public EstimatorResponse estimate(String file) throws RemoteException {
		
		EstimatorResponse response = new EstimatorResponse();
		response.setResourceName(file);
		
		eventQueue.clear();
		libraryBusy = false;
		time = 0;
		state = getResource().getHSMState(new VoidType());
		HSMFileInfo fileInfo = getResource().getHSMFileInfo(file);
		
		//if file is cached on hard disk do not perform simulation
		if(fileInfo.isIsCached()) {
			
			int startBlk = fileInfo.getStartBlock();
			int endBlk = fileInfo.getEndBlock();
			String tapeId = fileInfo.getTapeID();
			int blockSize = 1;
			
			TapeArray tapeMap = getResource().getHSMTapeMap(new VoidType());
			for(Tape tape : tapeMap.getTapeMap()) {
				
				if(tape.getTapeID().equals(tapeId)) {
					blockSize = tape.getBlockSize();
					break;
				}
			}
			float transferRate = getResource().getStorageSystemInfo(new VoidType())
				.getTransferRates()[0].getMeasuredTransferRate();
			response.setLatencyTime((long)((endBlk - startBlk) * blockSize * transferRate));
			return response;
		}
		
		filterFileQueue(file);
		
		//perform simulation
		Simulator sim = new Simulator(getResource(), this);
		boolean done = false;
		while(!done && state.getFileQueue().length > 0) {		
			
			//process event
			Event ev = eventQueue.poll();
			if(ev != null) {
				
				//update current time with event time
				time = ev.getFinishTime();
				

				if(ev instanceof FileLoaded) {
					//file loaded from tape to cache event
					FileLoaded loadedEv = (FileLoaded)ev;
			
					String loadedName = loadedEv.getFile().getName();
					if(loadedName.equals(file)) {
						done = true;
					}
					if(loadedEv.isLastForTape()) {
						String tapeId = loadedEv.getFile().getTapeID();
						HSMDriveState[] drives = state.getDrive();
						for(HSMDriveState drive : drives) {
							if(drive.getTapeID().equals(tapeId)) {
								drive.setUsage(USAGE_IDLE);
								drive.setCurrentPosition(loadedEv.getFile().getEndBlock());
								break;
							}
						}
					}
					
				} else if(ev instanceof TapeRollback) {
					//tape rollback ended event
					TapeRollback rollEv = (TapeRollback)ev;
					HSMDriveState drive = rollEv.getDrive();
					drive.setCurrentPosition(0);
					drive.setUsage(USAGE_IDLE);
					
				} else if(ev instanceof TapeInserted) {
					//tape mounted by robot and loaded to drive event
					
					TapeInserted insertEv = (TapeInserted)ev;
					HSMDriveState drive = insertEv.getDrive();
					drive.setCurrentPosition(0);
					drive.setUsage(USAGE_IDLE);
					libraryBusy = false;
					
				} else if(ev instanceof TapeRemoved) {
					//tape unloaded and removed by robot
					
					TapeRemoved removeEv = (TapeRemoved)ev;
					HSMDriveState drive = removeEv.getDrive();
					drive.setUsage(USAGE_EMPTY);
					libraryBusy = false;
					
				}
			}
			//process current state by simulator
			sim.processState(state, eventQueue);
			
		}
		hsmMon = null;
		
		response.setLatencyTime(time);
		return response;
	}
	
	private void filterFileQueue(String file) throws RemoteException {
		HSMFileInfo fileInfo = getResource().getHSMFileInfo(file);
		HSMFileInfo[] fileQueue = state.getFileQueue();
		
		//check if desired file is already in file queue
		String tapeId = fileInfo.getTapeID();
		boolean alreadyQueued = false;
		for(HSMFileInfo queuedFile : fileQueue) {
			
			if(queuedFile.getName().equals(fileInfo.getName())) {
				alreadyQueued = true;
			}
		}
		
		//insert file to queue (if not exists in it)
		if(!alreadyQueued) { 
		
			List<HSMFileInfo> temp = new ArrayList<HSMFileInfo>(Arrays.asList(fileQueue));
			temp.add(fileInfo);
			HSMFileInfo[] newFileInfo =  temp.toArray(new HSMFileInfo[0]);
			
			state.setFileQueue(newFileInfo);
		}
		
		LibrariesInfo libraries = getResource().getHSMLibraryInfo(new VoidType());
		
		//find library containing tape with desired file
		for(HSMLibrary library : libraries.getLibraries()) {
			
			Slot[] slots = library.getInventory();
			for(Slot slot : slots) {
				if(slot.getTapeID().equals(tapeId)) {
					desiredLibrary = library;
					break;
				}
			}
		}
		
		//remove from queue files from other libraries
		fileQueue = state.getFileQueue();
		List<HSMFileInfo> newFileQueue = new ArrayList<HSMFileInfo>();
		for(HSMFileInfo info : fileQueue) {
			
			String tid = info.getTapeID();
			boolean found = false;
			for(Slot s : desiredLibrary.getInventory()) {
				
				if(s.getTapeID().equals(tid)) {
					found = true;
					break;
				}
			}
			if(found) {
				newFileQueue.add(info);
			}
		}
		state.setFileQueue(newFileQueue.toArray(new HSMFileInfo[0]));
	}
	
	//get wsrf resource
	private HSMMonPortType getResource() {
		
		if (hsmMon == null) {
			HSMMonFactoryServiceAddressingLocator factoryLocator = new HSMMonFactoryServiceAddressingLocator();
			HSMMonServiceAddressingLocator instanceLocator = new HSMMonServiceAddressingLocator();
			
			try {
				String factoryURI = serviceAddress;
				EndpointReferenceType factoryEPR, instanceEPR;
				HSMMonFactoryPortType hsmMonFactory;
				
				factoryEPR = new EndpointReferenceType();
				factoryEPR.setAddress(new Address(factoryURI));
				hsmMonFactory = factoryLocator.getHSMMonFactoryPortTypePort(factoryEPR);
				System.err.println("\n Probuje odczytac dane o bibliotekach ze zrodla ...\n");
				CreateResource c = new CreateResource();
				CreateResourceResponse createResponse = hsmMonFactory.createResource(new CreateResource());
				instanceEPR = createResponse.getEndpointReference();
				hsmMon = instanceLocator.getHSMMonPortTypePort(instanceEPR);
		
			} catch (MalformedURIException ex ){
				System.err.println("\nBledny adres URL");
			} catch (Exception ex) {
				System.err.println("\nBlad w polaczeniu z serwisem - upewnij sie czy podales poprawny adres serwisu.\nJesli tak odczekaj chwile.");
			}
				}
		return hsmMon;
	}
	
	public long getCurrentTime() {
		return time;
	}
	public boolean isLibraryBusy() {
		return libraryBusy;
	}
	public void setLibraryBusy(boolean busy) {
		this.libraryBusy = busy;
	}
	public int getDesiredLibraryId() {
		return desiredLibrary.getId();
	}
	
	
	
}

